【AWS CDK】API Gateway で S3 をプロキシしてオブジェクトを削除してみた

【AWS CDK】API Gateway で S3 をプロキシしてオブジェクトを削除してみた

Clock Icon2021.05.21

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

テントの中から失礼します、CX事業本部のてんとタカハシです!

API Gateway では、S3 をプロキシして、オブジェクトのダウンロードやアップロード、削除など様々なことができます。単純な CRUD 操作であれば、Lambda を実装することなく実現可能なので結構便利です。

本記事では、API Gateway で S3 をプロキシして、Bucket に格納されているオブジェクトを削除する REST API を CDK で作成してみます。

下記のチュートリアルをガッツリ参考していますので、細かい説明についてはこちらをご参照ください。

他の記事で、オブジェクトのダウンロード、アップロード、一覧取得にもチャレンジしています。

本記事及び、上記の記事にて試した機能を全て搭載した REST API のソースコードを下記リポジトリに置いています。

GitHub - iam326/api-gateway-proxy-to-s3-by-cdk

環境

環境は下記の通りです。

$ cdk --version
1.102.0 (build a75d52f)

$ yarn --version
1.22.10

$ node --version
v14.7.0

実装

  • オブジェクトを格納するための S3 Bucket を作成する
  • API Gateway で REST API を作成する
  • 上記 API にリソース/users/{userId}/files/{fileName}を作成する
  • 上記リソースに DELETE メソッドを作成して、S3 をプロキシする
import * as cdk from '@aws-cdk/core';
import * as apigateway from '@aws-cdk/aws-apigateway';
import * as iam from '@aws-cdk/aws-iam';
import * as s3 from '@aws-cdk/aws-s3';

export class ApiGatewayProxyToS3ByCdkStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const projectName: string = this.node.tryGetContext('projectName');

    // ★ S3
    
    const bucket = new s3.Bucket(this, 'Bucket', {
      bucketName: `${projectName}-bucket`,
    });

    // ★ API Gateway
    
    const restApiRole = new iam.Role(this, 'Role', {
      assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
      path: '/',
    });
    bucket.grantReadWrite(restApiRole);

    const restApi = new apigateway.RestApi(this, 'RestApi', {
      restApiName: `${projectName}-api`,
      deployOptions: {
        stageName: 'v1',
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        dataTraceEnabled: true,
      },
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: ['POST', 'OPTIONS', 'PUT', 'DELETE'],
        statusCode: 200,
      },
    });

    // リソースを作成する `/users/{userId}/files/{fileName}`
    const users = restApi.root.addResource('users');
    const userId = users.addResource('{userId}');
    const files = userId.addResource('files');
    const fileName = files.addResource('{fileName}');
    
    // オブジェクトを削除するための DELETE メソッドを作成する
    fileName.addMethod(
      'DELETE',
      new apigateway.AwsIntegration({
        service: 's3',
        integrationHttpMethod: 'DELETE',
        // 削除するオブジェクトを指定する
        path: `${bucket.bucketName}/{folder}/{object}`,
        options: {
          credentialsRole: restApiRole,
          passthroughBehavior: apigateway.PassthroughBehavior.WHEN_NO_MATCH,
          requestParameters: {
            // メソッドリクエストのパスパラメータ userId を 統合リクエストのパスパラメータ folder にマッピングする
            'integration.request.path.folder': 'method.request.path.userId',
            // メソッドリクエストのパスパラメータ fileName を 統合リクエストの object にマッピングする
            'integration.request.path.object': 'method.request.path.fileName',
          },
          integrationResponses: [
            {
              statusCode: '200',
              responseParameters: {
                'method.response.header.Timestamp':
                  'integration.response.header.Date',
                'method.response.header.Content-Length':
                  'integration.response.header.Content-Length',
                'method.response.header.Content-Type':
                  'integration.response.header.Content-Type',
                'method.response.header.Access-Control-Allow-Headers':
                  "'Content-Type,Authorization'",
                'method.response.header.Access-Control-Allow-Methods':
                  "'OPTIONS,POST,PUT,GET,DELETE'",
                'method.response.header.Access-Control-Allow-Origin': "'*'",
              },
            },
            {
              statusCode: '400',
              selectionPattern: '4\d{2}',
              responseParameters: {
                'method.response.header.Access-Control-Allow-Headers':
                  "'Content-Type,Authorization'",
                'method.response.header.Access-Control-Allow-Methods':
                  "'OPTIONS,POST,PUT,GET,DELETE'",
                'method.response.header.Access-Control-Allow-Origin': "'*'",
              },
            },
            {
              statusCode: '500',
              selectionPattern: '5\d{2}',
              responseParameters: {
                'method.response.header.Access-Control-Allow-Headers':
                  "'Content-Type,Authorization'",
                'method.response.header.Access-Control-Allow-Methods':
                  "'OPTIONS,POST,PUT,GET,DELETE'",
                'method.response.header.Access-Control-Allow-Origin': "'*'",
              },
            },
          ],
        },
      }),
      {
        requestParameters: {
          'method.request.path.userId': true,
          'method.request.path.fileName': true,
        },
        methodResponses: [
          {
            statusCode: '200',
            responseParameters: {
              'method.response.header.Timestamp': true,
              'method.response.header.Content-Length': true,
              'method.response.header.Content-Type': true,
              'method.response.header.Access-Control-Allow-Headers': true,
              'method.response.header.Access-Control-Allow-Methods': true,
              'method.response.header.Access-Control-Allow-Origin': true,
            },
          },
          {
            statusCode: '400',
            responseParameters: {
              'method.response.header.Access-Control-Allow-Headers': true,
              'method.response.header.Access-Control-Allow-Methods': true,
              'method.response.header.Access-Control-Allow-Origin': true,
            },
          },
          {
            statusCode: '500',
            responseParameters: {
              'method.response.header.Access-Control-Allow-Headers': true,
              'method.response.header.Access-Control-Allow-Methods': true,
              'method.response.header.Access-Control-Allow-Origin': true,
            },
          },
        ],
      }
    );
  }
}

動作確認

テキトーなテキストファイルを作成して、直接 S3 にアップロードします。その後、API Gateway で作成した REST API にリクエストしてオブジェクトを削除してから、オブジェクトの一覧を取得します。Bucket の中身が空になっていることを確認できます。

$ cat sample.txt
hello, world

$ aws s3 cp sample.txt s3://<BUCKET_NAME>/sample-user/sample.txt
upload: ./sample.txt to s3://<BUCKET_NAME>/sample-user/sample.txt

$ aws s3api list-objects-v2 --bucket <BUCKET_NAME>
{
    "Contents": [
        {
            "Key": "sample-user/sample.txt",
            ...
        }
    ]
}

$ curl -X DELETE 
https://<DOMAIN_NAME>/v1/users/sample-user/files/sample.txt

$ aws s3api list-objects-v2 --bucket <BUCKET_NAME>

画像のアップロードやダウンロードの場合には、バイナリファイルに対応するための設定が必要ですが、削除の場合は差分ありません。

おわりに

本記事では、API Gateway で S3 をプロキシして、Bucket に格納されているオブジェクトを削除してみました。次回は、オブジェクト一覧の取得にチャレンジします。

今回は以上になります。最後まで読んで頂きありがとうございました!

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.